package org.spruice.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;

public class StringToValue {

    private static final Map<Class, Converter> defaults = loadDefaults();

    private static Map<Class, Converter> loadDefaults() {
        Map<Class, Converter> defaults = new HashMap<Class, Converter>();
        primitive(defaults, int.class, Integer.class);
        primitive(defaults, boolean.class, Boolean.class);
        primitive(defaults, long.class, Long.class);
        primitive(defaults, double.class, Double.class);
        primitive(defaults, float.class, Float.class);
        primitive(defaults, byte.class, Byte.class);
        primitive(defaults, short.class, Short.class);
        Converter none = new Converter() {

            public Object convert(String value) {
                return value;
            }
        };
        defaults.put(String.class, none);
        return defaults;
    }

    private final Map<Class, Converter> converters = new HashMap<Class, Converter>(defaults);

    private static void primitive(Map<Class, Converter> defaults, Class<?> prim, Class<?> objClzz) {
        Converter c = resolverConverter(objClzz);
        defaults.put(objClzz, c);
        defaults.put(prim, c);
    }

    public void register(Class clzz, Converter c) {
        converters.put(clzz, c);
    }

    public <T> T convert(Class<T> clzz, String value) {
        Converter<T> c = converters.get(clzz);
        if (c == null) {
            c = resolverConverter(clzz);
            register(clzz, c);
        }
        return c.convert(value);
    }

    private static <T> Converter<T> resolverConverter(Class<T> clzz) {
        try {
            final Method m = clzz.getMethod("valueOf", String.class);
            Modifier.isStatic(m.getModifiers());
            m.setAccessible(true);
            return new Converter<T>() {
                public T convert(String value) {
                    try {
                        return (T) m.invoke(null, value);
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    } catch (InvocationTargetException e) {
                        throw new RuntimeException(e);
                    }
                }
            };
        } catch (NoSuchMethodException e) {
            // ignore;
        }
        try {
            final Constructor<T> stringCtor = clzz.getConstructor(String.class);
            stringCtor.setAccessible(true);
            return new Converter<T>() {
                public T convert(String value) {
                    try {
                        return (T) stringCtor.newInstance(value);
                    } catch (InstantiationException e) {
                        throw new RuntimeException(e);
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    } catch (InvocationTargetException e) {
                        throw new RuntimeException(e);
                    }
                }
            };
        } catch (NoSuchMethodException e) {
        }
        throw new RuntimeException("Unable to resolve static valueOf(String) or constructor with single String argument for: " + clzz);
    }

    public interface Converter<V> {
        public V convert(String value);
    }
}
